#include "writtencharacter.h"

WrittenStroke::WrittenStroke()
{
    pointList = new QList();
    isAnalyzed = false;
}

WrittenStroke::~WrittenStroke()
{
    delete pointList;
}

//todo
WrittenPoint::WrittenPoint()
    : Point(x, y)
{
    
}

//todo
double WrittenPoint::getDistanceNormalized(WrittenPoint comparePoint)
{
    double width = WrittenCharacter.this.rightX - WrittenCharacter.this.leftX;
    double height = WrittenCharacter.this.bottomY - WrittenCharacter.this.topY;

    // normalizer is a diagonal along a square with sides of size the larger dimension of the bounding box
double dimensionSquared = width > height ? width * width : height * height;
double normalizer = Math.sqrt(dimensionSquared + dimensionSquared);

double distanceNormalized = this.distance(comparePoint) / normalizer;
distanceNormalized = Math.min(distanceNormalized, 1.0); 	// shouldn't be longer than 1 if it's normalized
return distanceNormalized;
}

//todo
double WrittenPoint::getDirection(WrittenPoint comparePoint)
{
double dx = this.getX() - comparePoint.getX();
double dy = this.getY() - comparePoint.getY();

double direction = Math.PI - Math.atan2(dy, dx);
return direction;
}
 
//todo
void WrittenStroke::addPoint(WrittenPoint point)
{
    int pointX = (int)point.getX();
    int pointY = (int)point.getY();

    // Expand the bounding box coordinates for this WrittenCharacter in necessary.
    WrittenCharacter.this.leftX		= Math.min(pointX, WrittenCharacter.this.leftX);
    WrittenCharacter.this.rightX	= Math.max(pointX, WrittenCharacter.this.rightX);
    WrittenCharacter.this.topY		= Math.min(pointY, WrittenCharacter.this.topY);
    WrittenCharacter.this.bottomY	= Math.max(pointY, WrittenCharacter.this.bottomY);

    this.pointList.add(point);
}

//todo
QList* WrittenStroke::getSubStrokes()
{
    if(!this->isAnalyzed) {
        this->analyzeAndMark();
    }

    QList* subStrokes = new QList();
    // Any WrittenStroke should have at least two points, (a single point cannot constitute a Stroke).
    // We should therefore be safe calling an iterator without checking for the first point.
    Iterator pointIter = this.pointList.iterator();			
    WrittenPoint previousPoint = (WrittenPoint)pointIter.next();

    while(pointIter.hasNext()) {
        WrittenPoint nextPoint = (WrittenPoint)pointIter.next();

        if(nextPoint.isPivot()) {
            // The direction from each previous point to each successive point, in radians.
            double direction = previousPoint.getDirection(nextPoint);

            // Use the normalized length, to account for relative character size.
            double normalizedLength = previousPoint.getDistanceNormalized(nextPoint);

            SubStrokeDescriptor *subStroke = new SubStrokeDescriptor(direction, normalizedLength);
            subStrokes.add(subStroke);

            previousPoint = nextPoint;
        }
    }

    return subStrokes;
}

//todo
void WrittenStroke::analyzeAndMark()
{
    	Iterator pointIter = this.pointList.iterator();
			
			// It should be impossible for a stroke to have < 2 points, so we are safe calling next() twice.
			WrittenPoint firstPoint = (WrittenPoint)pointIter.next();
			WrittenPoint previousPoint = firstPoint;
			WrittenPoint pivotPoint = (WrittenPoint)pointIter.next();
			
			// The first point of a Stroke is always a pivot point.
			firstPoint.setIsPivot(true);
			int subStrokeIndex = 1;
			
			// The first point and the next point are always part of the first SubStroke.
			firstPoint.setSubStrokeIndex(subStrokeIndex);
			pivotPoint.setSubStrokeIndex(subStrokeIndex);
			
			// localLength keeps track of the immediate distance between the latest three points.
			// We can use the localLength to find an abrupt change in SubStrokes, such as at a corner.
			// We do this by checking localLength against the distance between the first and last
			// of the three points.  If localLength is more than a certain amount longer than the
			// length between the first and last point, then there must have been a corner of some kind.
			double localLength = firstPoint.distance(pivotPoint);
			
			// runningLength keeps track of the length between the start of the current SubStroke
			// and the point we are currently examining.  If the runningLength becomes a certain
			// amount longer than the straight distance between the first point and the current
			// point, then there is a new SubStroke.  This accounts for a more gradual change
			// from one SubStroke segment to another, such as at a longish curve.
			double runningLength = localLength;
			
			// Iterate over the points, marking the appropriate ones as pivots.
			while(pointIter.hasNext()) {
				WrittenPoint nextPoint = (WrittenPoint)pointIter.next();
				
				// pivotPoint is the point we're currently examining to see if it's a pivot.
				// We get the distance between this point and the next point and add it
				// to the length sums we're using.
				double pivotLength = pivotPoint.distance(nextPoint);
				localLength += pivotLength;
				runningLength += pivotLength;
				
				// Check the lengths against the ratios.  If the lengths are a certain among
				// longer than a straight line between the first and last point, then we
				// mark the point as a pivot.
				if(localLength >= MAX_LOCAL_LENGTH_RATIO * previousPoint.distance(nextPoint) ||
				   runningLength >= MAX_RUNNING_LENGTH_RATIO * firstPoint.distance(nextPoint)) {
					
					if(previousPoint.isPivot() && previousPoint.distance(pivotPoint) < MIN_SEGMENT_LENGTH) {
						// If the previous point was a pivot and was very close to this point,
						// which we are about to mark as a pivot, then unmark the previous point as a pivot.
						// Also need to decrement the SubStroke that it belongs to since it's not part of
						// the new SubStroke that begins at this pivot.
						previousPoint.setIsPivot(false);
						previousPoint.setSubStrokeIndex(subStrokeIndex - 1);
					} else {
						// If we didn't have to unmark a previous pivot, then the we can increment the SubStrokeIndex.
						// If we did unmark a previous pivot, then the old count still applies and we don't need to increment.
						subStrokeIndex++;
					}
						
					pivotPoint.setIsPivot(true);
					
					// A new SubStroke has begun, so the runningLength gets reset.
					runningLength = pivotLength;
					
					firstPoint = pivotPoint;
				} 
				
				localLength = pivotLength;		// Always update the localLength, since it deals with the last three seen points.
				
				previousPoint = pivotPoint;
				pivotPoint = nextPoint;
				
				pivotPoint.setSubStrokeIndex(subStrokeIndex);
			}
				
			// last point (currently referenced by pivotPoint) has to be a pivot
			pivotPoint.setIsPivot(true);
			
			// Point before the final point may need to be handled specially.
			// Often mouse action will produce an unintended small segment at the end.
			// We'll want to unmark the previous point if it's also a pivot and very close to the lat point.
			// However if the previous point is the first point of the stroke, then don't unmark it, because then we'd only have one pivot.
			if(previousPoint.isPivot() &&
			   previousPoint.distance(pivotPoint) < MIN_SEGMENT_LENGTH &&
			   previousPoint != this.pointList.get(0)) {
				
				previousPoint.setIsPivot(false);
				pivotPoint.setSubStrokeIndex(subStrokeIndex - 1);
			}
			
			// Mark the stroke as analyzed so that it won't need to be analyzed again.
			this.isAnalyzed = true;
}

//WrittenCharacter
WrittenCharacter::WrittenCharacter()
{
    strokeList = new QList();
    this->resetEdges();
}

WrittenCharacter::~WrittenCharacter()
{
    
}

QList WrittenCharacter::getStrokeList()
{
    return this->strokeList;
}

void WrittenCharacter::addStroke(WrittenStroke stroke)
{
	this->strokeList.add(stroke);
}

void WrittenCharacter::clear()
{
	this->strokeList.clear();
	this->resetEdges();
}

void WrittenCharacter::resetEdges()
{
	this->leftX = Double.POSITIVE_INFINITY;
	this->rightX = Double.NEGATIVE_INFINITY;
	this->topY = Double.POSITIVE_INFINITY;
	this->bottomY = Double.NEGATIVE_INFINITY;
}

void WrittenCharacter::analyzeAndMark()
{
	for(Iterator strokeIter = this.strokeList.iterator(); strokeIter.hasNext();) {
			WrittenStroke nextStroke = (WrittenStroke)strokeIter.next();
		
			if(!nextStroke.isAnalyzed()) {
				// If the written character has not been analyzed yet, we need to analyze it.
				nextStroke.analyzeAndMark();
			}
		}
}

CharacterDescriptor WrittenCharacter::buildCharacterDescriptor()
{
		int strokeCount = this.strokeList.size();
		int subStrokeCount = 0;
		
		CharacterDescriptor descriptor = new CharacterDescriptor();
		
		double[] directions = descriptor.getDirections();
		double[] lengths = descriptor.getLengths();
		
		// Iterate over the WrittenStrokes, and translate them into CharacterDescriptor.SubStrokes.
		// Add all of the CharacterDescriptor.SubStrokes to the version.
		// When we run out of substroke positions we truncate all the remaining stroke and substroke information.
		for(Iterator strokeIter = this.strokeList.iterator(); strokeIter.hasNext() && subStrokeCount < CharacterDescriptor.MAX_CHARACTER_SUB_STROKE_COUNT;) {
			WrittenStroke nextStroke = (WrittenStroke)strokeIter.next();
			
			// Add each substroke's direction and length to the arrays.
			// All substrokes are lumped sequentially.  What strokes they
			// were a part of is not factored into the algorithm.
			// Don't run off the end of the array, if we do we just truncate.
			List subStrokes = nextStroke.getSubStrokes();
			for(Iterator subStrokeIter = subStrokes.iterator(); subStrokeIter.hasNext() && subStrokeCount < CharacterDescriptor.MAX_CHARACTER_SUB_STROKE_COUNT; subStrokeCount++) {
				SubStrokeDescriptor subStroke = (SubStrokeDescriptor)subStrokeIter.next();
				
				directions[subStrokeCount] = subStroke.direction;
				lengths[subStrokeCount] = subStroke.length;
			}
		}
		
		descriptor.setStrokeCount(strokeCount);
		descriptor.setSubStrokeCount(subStrokeCount);
		
		return descriptor;
}